/**
 * Copyright (c) 2008, Mu Dynamics.
 *  All rights reserved.
 *  
 *   Redistribution and use in source and binary forms, with or without modification, 
 *   are permitted provided that the following conditions are met:
 *   
 *  - Redistributions of source code must retain the above copyright notice, 
 *     this list of conditions and the following disclaimer.
 *  - Redistributions in binary form must reproduce the above copyright notice, 
 *     this list of conditions and the following disclaimer in the documentation and/or 
 *     other materials provided with the distribution.
 *  - Neither the name of the "Mu Dynamics" nor the names of its contributors may be used 
 *     to endorse  or promote products derived from this software without specific prior 
 *     written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
 * THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT 
 * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package com.mu.jacob.core.generator;

import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.TypeParameterElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.ArrayType;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.ErrorType;
import javax.lang.model.type.ExecutableType;
import javax.lang.model.type.NoType;
import javax.lang.model.type.NullType;
import javax.lang.model.type.PrimitiveType;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.type.TypeVariable;
import javax.lang.model.type.WildcardType;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;

import com.google.inject.Binder;
import com.google.inject.Module;
import com.mu.jacob.core.model.decorator.DefaultAnnotationMirror;
import com.mu.jacob.core.model.decorator.DefaultAnnotationValue;
import com.mu.jacob.core.model.decorator.DefaultArrayType;
import com.mu.jacob.core.model.decorator.DefaultDeclaredType;
import com.mu.jacob.core.model.decorator.DefaultDecorator;
import com.mu.jacob.core.model.decorator.DefaultElement;
import com.mu.jacob.core.model.decorator.DefaultElements;
import com.mu.jacob.core.model.decorator.DefaultErrorType;
import com.mu.jacob.core.model.decorator.DefaultExecutableElement;
import com.mu.jacob.core.model.decorator.DefaultExecutableType;
import com.mu.jacob.core.model.decorator.DefaultNoType;
import com.mu.jacob.core.model.decorator.DefaultNullType;
import com.mu.jacob.core.model.decorator.DefaultPackageElement;
import com.mu.jacob.core.model.decorator.DefaultPrimitiveType;
import com.mu.jacob.core.model.decorator.DefaultTypeElement;
import com.mu.jacob.core.model.decorator.DefaultTypeMirror;
import com.mu.jacob.core.model.decorator.DefaultTypeParameterElement;
import com.mu.jacob.core.model.decorator.DefaultTypeVariable;
import com.mu.jacob.core.model.decorator.DefaultTypes;
import com.mu.jacob.core.model.decorator.DefaultVariableElement;
import com.mu.jacob.core.model.decorator.DefaultWildcardType;

/**
 * Element module defines the mapping
 * from Java compiler API interfaces to default decorators.
 * Override this class to provide custom mapping
 * and use registerDecorator() method to register
 * custom decorators. Every custom decorator should
 * derive from a default decorator and have the default
 * constructor only.
 * @author Adam Smyczek
 */
public class ElementModule implements Module {

	/* Decorator map */
	private final Map<Class<?>, Class<? extends DefaultDecorator>> elementMap 
		= new HashMap<Class<?>, Class<? extends DefaultDecorator>>();
	
	/**
	 * Constructor initializes map with default decorators
	 */
	public ElementModule() {
		super();
		
		elementMap.put(ArrayType.class, DefaultArrayType.class);
		elementMap.put(DeclaredType.class, DefaultDeclaredType.class);
		elementMap.put(ErrorType.class, DefaultErrorType.class);
		elementMap.put(ExecutableType.class, DefaultExecutableType.class);
		elementMap.put(NoType.class, DefaultNoType.class);
		elementMap.put(NullType.class, DefaultNullType.class);
		elementMap.put(PrimitiveType.class, DefaultPrimitiveType.class);
		elementMap.put(TypeVariable.class, DefaultTypeVariable.class);
		elementMap.put(WildcardType.class, DefaultWildcardType.class);
		elementMap.put(TypeMirror.class, DefaultTypeMirror.class);
		
		// bind elements
		elementMap.put(PackageElement.class, DefaultPackageElement.class);
		elementMap.put(TypeElement.class, DefaultTypeElement.class);
		elementMap.put(ExecutableElement.class, DefaultExecutableElement.class);
		elementMap.put(TypeParameterElement.class, DefaultTypeParameterElement.class);
		elementMap.put(VariableElement.class, DefaultVariableElement.class);
		elementMap.put(Element.class, DefaultElement.class);
		
		// other model elements
		elementMap.put(AnnotationMirror.class, DefaultAnnotationMirror.class);
		elementMap.put(AnnotationValue.class, DefaultAnnotationValue.class);
		elementMap.put(Elements.class, DefaultElements.class);
		elementMap.put(Types.class, DefaultTypes.class);
	}

	/**
	 * Register decorator for a type
	 * @param type
	 * @param decorator
	 */
	protected void registerDecorator(final DefaultDecorator decorator) {
		elementMap.put(decorator.getApiClass(), decorator.getClass());
	}
	
	/**
	 * Bind mappings
	 */
	@SuppressWarnings("unchecked")
	public final void configure(Binder binder) {
		for (Entry<Class<?>, Class<? extends DefaultDecorator>> e : elementMap.entrySet()) {
			binder.bind(e.getKey()).to((Class)e.getValue());
		}
	}

}
